<html lang="zh">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport"
      content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
    <script type="text/javascript"
      src="./rtc/adapter-latest.js"></script>
    <script type="text/javascript"
      src='./js/uni.webview.js'></script>
    <script type="text/javascript"
      src='./js/utils.js'></script>
    <script type="text/javascript"
      src='./js/jsonly.js'></script>
    <style lang="scss">
      body {
        padding: 0;
        margin: 0;
        background-image: url('image/wallpaper.png');
        background-size: contain;
      }

      .webrtc-box {
        background: #666;
        border-radius: 6px;
        width: 100%;
        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      }

      .localvideo {
        width: 100vw;
        height: 100vh;
        object-fit: cover;
      }

      .remotevideo {
        min-height: 160px;
        width: 100px;
        position: fixed;
        /* top: 40px; */
        top: calc(var(--status-bar-height) + 40px);
        right: 15px;
        z-index: 10;
        object-fit: cover;
      }

      .call-user-box {
        position: fixed;
        bottom: 20px;
        width: 100%;
      }

      .call-user {
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
        margin-bottom: 50px;
      }

      .call-user .avatar {
        width: 60px;
        height: 60px;
        object-fit: contain;
        border-radius: 50%;
        overflow: hidden;
      }

      .call-user .text {
        font-size: 16px;
        margin-top: 15px;
        color: #f6f6f6
      }

      .call-time {
        color: #f6f6f6;
        font-size: 24px;
        text-align: center;
      }

      .calling-button {
        display: flex;
        justify-content: space-around;
        padding: 20px;
      }

      .calling-button .button {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
      }

      .calling-button .button .image {
        width: 60px;
        height: 60px;
        margin-bottom: 10px;
      }

      .calling-button .button .text {
        color: #f6f6f6;
      }

      .calling-button .switch-btn .text {
        font-size: 12px !important;
      }

      .calling-button .button .image-icon {
        width: 40px;
        height: 40px;
        margin-bottom: 10px;
      }

      .joinRtc-list {
        width: 100%;
        height: 60vh;
        max-height: 60vh;
        display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        justify-content: center;
        overflow-y: auto;
      }

      .joinRtc-item {
        padding: 8px;
        color: #fff;
        display: flex;
        flex-direction: column;
        align-items: center;
      }

      .joinRtc-img {
        width: 60px;
        height: 60px;
        object-fit: cover;
        border-radius: 4px;
      }

      .joinRtc-name {
        font-size: 12px;
        margin-top: 4px;
        color: #fff
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="webrtc-box">
        <audio id="music1">
          <source src="http://im-file.jwbl.top/audio/call.mp3">
        </audio>
        <video v-show="localStream && is_video"
          class="localvideo"
          ref="localvideo"
          x5-video-player-fullscreen="true"
          autoplay
          x5-playsinline
          playsinline
          webkit-playsinline
          @click="displayBtn = !displayBtn"
          poster="./image/wallpaper.png"></video>
        <video v-show="remoteStream && is_video"
          class="remotevideo"
          ref="remotevideo"
          x5-video-player-fullscreen="true"
          autoplay
          x5-playsinline
          playsinline
          webkit-playsinline
          @click="changeVideo()"
          poster="./image/wallpaper.png"></video>
        <div class="call-user-box"
          v-if="displayBtn">
          <div v-if="groupRtcShow"
            class="joinRtc-list">
            <div v-for="item in joinRtc"
              class="joinRtc-item">

              <img class="joinRtc-img"
                :src="item.avatar" />
            </div>

          </div>
          <div class="call-user"
            v-else-if="contact">
            <img class="avatar"
              v-if="status!=2 || !is_video"
              :src="contact.avatar"
              alt="">
            <div class="text">
              <b v-if="!is_video && status==2">{{contact.displayName}}</b>
              <span v-if="status!=2">
                <span v-if="status==3"> {{contact.displayName}} 正在请求与您{{is_video ? '视频' : '语音'}}通话</span>
                <span v-else>您正对 <b>{{contact.displayName}}</b> 发起{{is_video ? '视频' : '语音'}}通话</span>
              </span>

            </div>
          </div>
          <div class="call-time"
            v-if="callTime && status==2">
            {{setCallTime()}}
          </div>
          <div class="calling-button">
            <div class="button"
              v-if="status!=0">
              <img class="image"
                src="./image/guaduan.png"
                @click="hangup(true)" />
              <div class="text">挂断</div>
            </div>
            <div class="button switch-btn"
              v-if="status<3">
              <img class="image-icon"
                :src="'./image/voice'+(voiceStatus ? '' : '-off')+'.png'"
                @click="switchVoice()" />
              <div class="text">{{ voiceStatus ? '关闭' : '开启'}}麦克风</div>
            </div>
            <div class="button"
              v-if="status==3">
              <img class="image"
                src="./image/jieting.png"
                @click="answer()" />
              <div class="text">接听</div>
            </div>
            <div class="button switch-btn"
              v-if="status<3">
              <template v-if="is_video">
                <img class="image-icon"
                  :src="'./image/video.png'"
                  @click="exchangeVideo()" />
                <div class="text">切换摄像头</div>
              </template>
              <template v-else>
                <img class="image-icon"
                  :src="'./image/speaker'+(speaker ? '' : '-off')+'.png'"
                  @click="speakBtn()" />
                <div class="text">{{ speaker ? '关闭' : '开启'}}扬声器</div>
              </template>
            </div>
          </div>
        </div>
      </div>
    </div>
  </body>
  <script type="text/javascript"
    src='./js/vue.js'></script>
  <script>
    // 如果 window 对象不存在，则简单赋值一个全局对象（谨慎使用）
    if (typeof window === 'undefined') {
      global.window = globalThis;
    }

    // 获取 URL 参数：在 H5 下用 window.location.href，否则使用 plus.webview
    let currentUrl = '';
    if (typeof window !== 'undefined' && window.location && window.location.href) {
      currentUrl = window.location.href;
    } else if (typeof plus !== 'undefined' && plus.webview && plus.webview.currentWebview) {
      currentUrl = plus.webview.currentWebview().getURL();
    }
    const params = parseUrl(currentUrl);

    // const params = parseUrl(window.location.href);
    const opt = JSON.parse(decodeURIComponent(params.stun));
    const config = {
      'iceServers': [{
        'urls': ['stun:stun.xten.com', 'stun:stun.l.google.com:19302', 'stun:stun1.l.google.com:19302',
          'stun:stun2.l.google.com:19302', 'stun:stun3.l.google.com:19302', 'stun:stun4.l.google.com:19302'
        ]
      }, {
        'urls': opt.stun ? opt.stun : ['stun:stun.callwithus.com'], // 自己搭建服务器地址
        "username": opt.stunUser ? opt.stunUser : '',
        "credential": opt.stunPass ? opt.stunPass : ''
      }],
    };
    const Counter = {
      data() {
        return {
          joinRtc: [],
          groupRtcShow: false,
          displayBtn: true,
          platform: params.platform,
          status: 0, //状态0，默认，1：拨号中，2通话中，3来电中，4忙线
          pc: null, //pc实力化
          localVideo: "", //本地视频的DOM
          remoteVideo: "", //远程视频的DOM
          remoteStream: null, // 远端视频流
          localStream: null, // 本地视频流
          is_video: 0, //是否为视频通话
          videoStatus: true, //视频开启状态
          voiceStatus: true, //语音开启状态
          cutdown: 40, //拨号超时
          timer: null, //计时器
          offerParams: {},
          plus: null,
          streamType: 1, //视频通话展示方式
          facingMode: 'user', //前置摄像头还是后置摄像头 user-前置 environment-后置
          headset: true, //麦克风 打开true 关闭false,
          senders: null, // 数据流
          speaker: true, // 听筒 false 扬声器true
          callTime: 0, //通话时间
          callTimeDis: '', //通话时间展示
          timerIntervalId: null, //通话计时器
          contact: {
            call_user_id: params.call_user_id,
            id: params.target_id,
            displayName: params.name,
            avatar: params.avatar
          },
        };
      },
      mounted() {
        this.pc = new RTCPeerConnection(config);
        this.pc.ontrack = (event) => {
          console.log(event, '远端视频流');
          if (this.localVideo) {
            this.remoteStream = event.streams[0];
            setTimeout(() => {
              this.streamType = 2;
            }, 50)
          }
        };

        if (this.platform === 'app') {
          document?.addEventListener('plusready', () => {
            console.log('设置扬声器')
            this.plus = plus.audio.createPlayer();
            this.plus.setRoute(plus.audio.ROUTE_SPEAKER);
          });
        }

        this.localVideo = this.$refs.localvideo;
        this.remoteVideo = this.$refs.remotevideo;

        window?.addEventListener('message', (e) => {
          this.callMessagecallback(e);
        }, false);

        // window.getUniAppMessage = (arg) => {
        //   const data = {
        //     data: jsonly(arg)
        //   }

        //   this.callMessagecallback(data);
        // }
        // 定义全局消息处理方法
        if (typeof window !== 'undefined') {
          window.getUniAppMessage = (arg) => {
            const data = {
              data: jsonly(arg)
            }
            this.callMessagecallback(data);
          }
        } else if (typeof plus !== 'undefined') {
          plus.webview.currentWebview().getUniAppMessage = (arg) => {
            const data = {
              data: jsonly(arg)
            }
            this.callMessagecallback(data);
          }
        }


        this.is_video = params.type == 1 ? true : false;

        this.offerParams = this.is_video ? {
          offerToRecieveAudio: 1,
          offerToRecieveVideo: 1
        } : {
          offerToRecieveAudio: 1,
          offerToRecieveVideo: 0
        }
        this.status = params.status
        // 如果状态为1,表示拨打电话，并且calling状态为1的时候才是直接拨打；
        if (this.status == 1) {
          if (params.calling == 1) {
            this.called(this.is_video)
          }
        } else {
          this.playMusicCall('state');
        }

      },
      watch: {
        streamType(val) {
          // 切换镜头位置
          if (val == 1) {
            this.localVideo.srcObject = this.localStream;
            this.remoteVideo.srcObject = this.remoteStream;
            this.localVideo.muted = true;
            this.remoteVideo.muted = false;
          } else {
            this.localVideo.srcObject = this.remoteStream;
            this.remoteVideo.srcObject = this.localStream;
            this.localVideo.muted = false;
            this.remoteVideo.muted = true;
          }
        }
      },
      computed: {
        group_webrtc_user: () => {
          const data = localStorage.getItem('group_webrtc_user')?.data;
          return data
        }
      },
      methods: {
        // 开始通话计时
        startTime() {
          this.timerIntervalId = setInterval(() => {
            this.callTime++
          }, 1000)
        },
        // 设置通话时间
        setCallTime() {
          let time = this.callTime;
          const hours = Math.floor(time / 3600);
          const minutes = Math.floor((time - (hours * 3600)) / 60);
          const seconds = time - (hours * 3600) - (minutes * 60);
          return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
        },
        // 视频电话初始化本地视频
        initLocalStream(call_id, is_video) {
          let video = is_video;
          if (is_video) {
            video = {
              width: window.screen.height,
              height: window.screen.width
            }
          }
          navigator.mediaDevices.getUserMedia({
            video: video,
            audio: {
              echoCancellation: true
            }
          }).then((stream) => {
            console.log(stream, '接听')
            this.localStream = stream;
            // 同步音频
            stream.getTracks().forEach((track) => {
              this.pc.addTrack(track, stream);
            });
            this.localVideo.srcObject = this.localStream;
            // 把自己的视频静音
            this.localVideo.muted = true;
            if (call_id) {
              this.postMsg({
                event: 'calling',
                status: 3,
                code: 901
              });
              // 计时器，如果一段时间没有接听则自动挂断
              this.timer = setInterval(() => {
                this.cutdown--;
                if (this.cutdown == 0) {
                  this.hangup(true);
                }
              }, 1000)
            } else {
              // 告诉对方已经接听电话
              this.postMsg({
                event: 'acceptRtc',
                code: 904
              });
            }
            // 监听远程媒体流
          }).catch((e) => {
            this.postMsg({
              event: 'mediaDevices',
            })
          });
        },
        // 拨打电话
        called(is_video) {
          this.is_video = is_video;
          this.initLocalStream(true, is_video);
          this.playMusicCall('state');
        },
        // 接听电话
        answer() {
          console.log('接听电话')
          this.status = 2;
          this.initLocalStream(false, this.is_video);
          this.playMusicCall('close');
          this.startTime();
        },
        // 挂断电话
        hangup(btn) {
          clearInterval(this.timer);
          clearInterval(this.timerIntervalId);
          if (this.status != 2) {
            this.playMusicCall('close');
          }
          if (this.status) {
            this.closeLocalMedia(); //关闭本地媒体
            this.remoteStream = null; //关闭远程媒体
          }
          // 通话取消
          let code = 902;
          // 通话中挂断
          if (this.status == 2) {
            code = 906
            // 拒绝挂断
          } else if (this.status == 3) {
            code = 903
            //对方忙线中
          } else if (this.status == 4) {
            code = 907
          }
          this.postMsg({
            event: 'hangup',
            isbtn: btn,
            callTime: this.callTime,
            code: code
          })
        },
        // 关闭本地媒体
        closeLocalMedia() {
          if (this.localStream && this.localStream.getTracks()) {
            this.localStream.getTracks().forEach((track) => {
              track.stop();
            });
          }
          this.localStream = null;
        },
        // 打开或关闭声音
        switchVoice() {
          if (this.localStream == null) {
            alert('请打开音视频');
            return false;
          }
          const tracks = this.localStream.getTracks();
          if (this.voiceStatus) {
            tracks.forEach(track => {
              if (track.kind === 'audio') {
                track.enabled = false
              }
            });
            this.voiceStatus = false;
          } else {
            tracks.forEach(track => {
              if (track.kind === 'audio') {
                track.enabled = true
              }
            });
            this.voiceStatus = true;
          }
        },
        // 临时开、关视频
        switchVideo() {
          if (this.localStream == null) {
            alert('请打开音视频');
            return false;
          }
          const tracks = this.localStream.getTracks();
          if (this.videoStatus) {
            tracks.forEach(track => {
              if (track.kind === 'video') {
                track.enabled = false
              }
            });
            this.videoStatus = false;
          } else {
            tracks.forEach(track => {
              if (track.kind === 'video') {
                track.enabled = true
              }
            });
            this.videoStatus = true;
          }
        },
        // 切换前后摄像头
        exchangeVideo() {
          this.localStream.getTracks().forEach(track => track.stop());
          if (this.facingMode == 'user') this.facingMode = 'environment'
          else this.facingMode = 'user'
          navigator.mediaDevices.getUserMedia({
            video: {
              width: window.screen.height,
              height: window.screen.width,
              facingMode: {
                exact: this.facingMode
              }
            },
            audio: {
              echoCancellation: true,
            }
          }).then((mediastream) => {
            this.senders = this.pc.getSenders()
            let videoTrack = mediastream.getVideoTracks()[0];
            let audioTrack = mediastream.getAudioTracks()[0];
            var sender = this.senders.find((s) => {
              return s.track.kind == 'video';
            });
            var sender2 = this.senders.find((s) => {
              return s.track.kind == 'audio';
            });
            sender.replaceTrack(videoTrack);
            sender2.replaceTrack(audioTrack);
            if (this.streamType === 2) this.remoteVideo.srcObject = mediastream;
            else this.localVideo.srcObject = mediastream
            this.localStream = mediastream
            if (this.voiceStatus == false) {
              this.voiceStatus = true;
              this.switchVoice();
            }
            if (this.speaker) {
              this.speaker = !this.speaker
            } else {
              this.speaker = !this.speaker
            }
          })
        },
        // 播放响铃
        playMusicCall(type) {
          var audio = document.getElementById("music1");
          console.log('播放响铃', type == 'close' && !audio.paused)
          if (type == 'close' && !audio.paused) {
            audio.pause(); // 暂停
            return;
          }
          if (type === "state") {
            audio.loop = true;
          } else {
            audio.loop = false;
          }

          if (audio.paused) {
            audio.play(); // 播放
          } else {
            audio.pause(); // 暂停
          }
        },
        // 向uniapp发送消息，页面通讯
        postMsg(data) {
          if (this.platform === 'app') {
            uni.postMessage({
              data: data
            })
          } else {
            window.parent.postMessage(data)
          }
        },
        // 接收websocket发送过来的消息,由uniapp接收后传输到当前页面
        callMessagecallback(msg) {
          let e = msg.data;

          switch (e.event) {
            case "updataUsers":
              this.groupRtcShow = true;
              this.joinRtc = [...e.data];
              break;
            case "calling":
              console.log('发起通话...');
              this.called(this.is_video);
              break;
            case "hangup":
              this.hangup(false);
              break;
            case "busy":
              this.status = 4;
              this.hangup(false);
              break;
            case "acceptRtc": //已经接听，创建offer并发送
              this.status = 2;
              clearInterval(this.timer);
              this.startTime();
              this.playMusicCall();
              this.createOffer()
              break;
            case "turndown":
              break;
            case "answer":
              //同步answer信息...
              this.pc.setRemoteDescription(new RTCSessionDescription({
                type: 'answer',
                sdp: e.sdp
              }));
              break;
            case "iceCandidate":
              setTimeout(() => {
                // 添加ice完成通话连接
                if (typeof(e.iceCandidate) === 'object') {
                  this.pc.addIceCandidate(new RTCIceCandidate(e.iceCandidate));
                } else {
                  this.pc.addIceCandidate(new RTCIceCandidate(JSON.parse(e.iceCandidate)));
                }
              }, 100)
              break;
            case "offer":
              this.pc.setRemoteDescription(new RTCSessionDescription({
                type: 'offer',
                sdp: e.sdp
              }));
              this.createAnswer();
              break;

          }
        },
        // 创建offer-sdp
        createOffer() {
          this.pc.createOffer(this.offerParams).then((offer) => {
            this.pc.setLocalDescription(offer);
            this.postMsg({
              event: 'offer',
              sdp: offer.sdp
            }, '*');
          });
          // 创建offer需要监听ice流
          this.onicecandidate();
        },
        // 创建应答sdp
        createAnswer() {
          this.pc.createAnswer(this.offerParams).then((answer) => {
            this.pc.setLocalDescription(answer);
            this.postMsg({
              event: 'answer',
              sdp: answer.sdp
            }, '*');
            this.onicecandidate();
          });
        },
        onicecandidate() {
          this.pc.onicecandidate = (event) => {
            var iceCandidate = event.candidate;
            if (iceCandidate) {
              this.postMsg({
                event: 'iceCandidate',
                iceCandidate: JSON.parse(JSON.stringify(iceCandidate))
              }, '*');
            }
          };
        },
        //切换视频显示位置
        changeVideo() {
          this.streamType == 1 ? this.streamType = 2 : this.streamType = 1;
        },
        //打开关闭扬声器 h5端就是静音 ROUTE_EARPIECE 听筒 ROUTE_SPEAKER 扬声器
        speakBtn() {
          if (this.speaker) { //扬声器 => 听筒    
            this.speaker = !this.speaker
            if (this.platform === 'h5') {
              this.localVideo.muted = true
            }
            if (this.platform === 'app') {
              this.plus.setRoute(plus.audio.ROUTE_EARPIECE);
            }
          } else { //听筒 => 扬声器
            this.speaker = !this.speaker
            if (this.platform === 'h5') {
              this.localVideo.muted = false
            }
            if (this.platform === 'app') {
              this.plus.setRoute(plus.audio.ROUTE_SPEAKER);
            }
          }
        }
      }
    }

    const app = Vue.createApp(Counter);
    app.mount('#app');
  </script>
</html>